package tu.java.db;

import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Date;
import java.util.Enumeration;
import java.util.Vector;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This inner class represents a connection pool. It creates new connections on
 * demand, up to a max number if specified. It also makes sure a connection is
 * still open before it is returned to a client.
 */
public class DBConnPool {
	
	
	
	private Logger			log				= LoggerFactory.getLogger(DBConnPool.class);
	private int				checkedOut;
	private Vector			freeConnections	= new Vector();
	private int				maxConn;
	private String			name;
	private String			password;
	private String			URL;
	private String			user;
	private String			sequenceImpl;
	private DBConnManager	mgr;
	
	
	
	/**
	 * Creates new connection pool.
	 * 
	 * @param name
	 * The pool name
	 * @param URL
	 * The JDBC URL for the database
	 * @param user
	 * The database user, or null
	 * @param password
	 * The database user password, or null
	 * @param maxConn
	 * The maximal number of connections, or 0 for no limit
	 */
	public DBConnPool(DBConnManager mgr, String name, String URL, String user, String password, int maxConn, String sequenceImpl) {
		// Initialize members
		this.name = name;
		this.URL = URL;
		this.user = user;
		this.password = password;
		this.maxConn = maxConn;
		this.mgr = mgr;
		this.sequenceImpl = sequenceImpl;
	}
	
	
	
	public Sequence getSequence(String sSeqName, int iFirstValue) {
		Sequence aSeq = null;
		try {
			Method m = Class.forName(sequenceImpl).getMethod("createSequence", new Class[] { DBConnPool.class, String.class, Integer.class });
			
			aSeq = (Sequence) m.invoke(null, new Object[] { this, sSeqName, new Integer(iFirstValue) });
		}
		catch (Exception e) {
			log.error("Unable to invoke Sequence creation for <{}>", sequenceImpl, e);
		}
		
		return aSeq;
	}
	
	
	
	public Sequence getSequence(String sPkName, String sTableName, int iFirstValue) {
		Sequence aSeq = null;
		
		try {
			Method m = Class.forName(sequenceImpl).getMethod("createSequence", new Class[] { DBConnPool.class, String.class, String.class, Integer.class });
			
			aSeq = (Sequence) m.invoke(null, new Object[] { this, sPkName, sTableName, new Integer(iFirstValue) });
		}
		catch (Exception e) {
			log.error("Unable to invoke Sequence creation for <{}>", sequenceImpl, e);
		}
		
		return aSeq;
	}
	
	
	
	/**
	 * Checks in a connection to the pool. Notify other Threads that may be
	 * waiting for a connection.
	 * 
	 * @param con
	 * The connection to check in
	 */
	public synchronized void freeConnection(Connection con) {
		// Put the connection at the end of the Vector
		freeConnections.addElement(con);
		checkedOut--;
		notifyAll();
	}
	
	
	
	/**
	 * Checks out a connection from the pool. If no free connection is
	 * available, a new connection is created unless the max number of
	 * connections has been reached. If a free connection has been closed by the
	 * database, it's removed from the pool and this method is called again
	 * recursively.
	 */
	public synchronized Connection getConnection() throws SQLException {
		Connection con = null;
		
		if (freeConnections.size() > 0) {
			// Pick the first Connection in the Vector
			// to get round-robin usage
			con = (Connection) freeConnections.firstElement();
			freeConnections.removeElementAt(0);
			
			try {
				if (con.isClosed()) {
					log.info("Removed bad connection from <" + name + ">");
					
					// Try again recursively
					con = getConnection();
				}
			}
			catch (SQLException e) {
				log.info("Removed bad connection from <" + name + ">");
				
				// Try again recursively
				con = getConnection();
			}
		}
		else if ((maxConn == 0) || (checkedOut < maxConn)) {
			con = newConnection();
		}
		else {
			log.debug(">>> " + this.getClass().getName() + " " + "pool trop bas : maxconnection " + maxConn + " checked :" + checkedOut);
		}
		
		if (con != null) {
			checkedOut++;
		}
		
		return con;
	}
	
	
	
	/**
	 * Checks out a connection from the pool. If no free connection is
	 * available, a new connection is created unless the max number of
	 * connections has been reached. If a free connection has been closed by the
	 * database, it's removed from the pool and this method is called again
	 * recursively.
	 * <P>
	 * If no connection is available and the max number has been reached, this
	 * method waits the specified time for one to be checked in.
	 * 
	 * @param timeout
	 * The timeout value in milliseconds
	 */
	public synchronized Connection getConnection(long timeout) throws SQLException {
		long startTime = new Date().getTime();
		Connection con;
		
		while ((con = getConnection()) == null) {
			try {
				wait(timeout);
			}
			catch (InterruptedException e) {
			}
			
			if ((new Date().getTime() - startTime) >= timeout) {
				// Timeout has expired
				return null;
			}
		}
		
		return con;
	}
	
	
	
	/**
	 * Closes all available connections.
	 */
	public synchronized void release() {
		Enumeration allConnections = freeConnections.elements();
		
		while (allConnections.hasMoreElements()) {
			Connection con = (Connection) allConnections.nextElement();
			
			try {
				con.close();
				log.debug("Closed connection for pool <" + name + ">");
			}
			catch (SQLException e) {
				log.error("Can't close connection for pool <{}>", name, e);
			}
		}
		
		freeConnections.removeAllElements();
	}
	
	
	
	public String getPoolName() {
		return name;
	}
	
	
	
	/**
	 * Creates a new connection, using a userid and password if specified.
	 */
	private Connection newConnection() throws SQLException {
		Connection con = null;
		
		try {
			if (user == null) {
				con = DriverManager.getConnection(URL);
			}
			else {				
				con = DriverManager.getConnection(URL, user, password);
			}
			
			log.debug(">>> " + this.getClass().getName() + " => Created a new connection in pool : [" + name + "]");
		}
		catch (SQLException e) {
			log.error(">>> Can't create a new connection for <{}>. See the stack trace below.", URL, e);
			throw e;
		}
		
		return con;
	}
	
	
	
	/**
	 * @return
	 */
	public String getName() {
		return name;
	}
	
	
	
	/**
	 * @return
	 */
	public String getPassword() {
		return password;
	}
	
	
	
	/**
	 * @return
	 */
	public String getURL() {
		return URL;
	}
	
	
	
	/**
	 * @return
	 */
	public String getUser() {
		return user;
	}
	
	
	
	/**
	 * @param string
	 */
	public void setUser(String string) {
		user = string;
	}
	
}
